package com.dongrj.framework.core.utils;

import lombok.extern.slf4j.Slf4j;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;


@Slf4j
public final class DateTimeUtils {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

    public static final String DEFAULT_DATE_ZONE = "Asia/Shanghai";

    private static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT);

    public static final ZoneId DEFAULT_ZONE = ZoneId.of(DEFAULT_DATE_ZONE);

    public static String toString(LocalDate localDate, String format) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
        return localDate.format(formatter);
    }

    public static String toString(LocalDateTime localDateTime, String format) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
        return localDateTime.format(formatter);
    }

    public static String toString(LocalDate localDate) {
        return localDate.format(DEFAULT_FORMATTER);
    }

    public static String toString(LocalDateTime localDateTime) {
        return localDateTime.format(DEFAULT_FORMATTER);
    }

    public static Long timestamp(LocalDateTime localDateTime) {
        ZoneId east8Zone = ZoneId.of(DEFAULT_DATE_ZONE);
        return localDateTime.atZone(east8Zone).toInstant().toEpochMilli();
    }

    public static LocalDateTime toLocalDateTime(String timeStr) {
        return toLocalDateTime(timeStr, DEFAULT_DATE_FORMAT);
    }

    public static LocalDateTime toLocalDateTime(String timeStr, String format) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
        return LocalDateTime.parse(timeStr, formatter);
    }

    public static LocalDateTime millisToDateTime(long millisTimestamp) {
        return LocalDateTime.ofInstant(Instant.ofEpochMilli(millisTimestamp), DEFAULT_ZONE);
    }

    public static LocalDateTime secondsToDateTime(long secondsTimestamp) {
        return LocalDateTime.ofInstant(Instant.ofEpochSecond(secondsTimestamp), DEFAULT_ZONE);
    }

    public static long betweenDayNum(String dateTime1, String dateTime2) {
        LocalDateTime localDateTime1 = toLocalDateTime(dateTime1);
        LocalDateTime localDateTime2 = toLocalDateTime(dateTime2);
        return betweenDayNum(localDateTime1, localDateTime2);
    }

    public static long betweenDayNum(LocalDateTime dateTime1, LocalDateTime dateTime2) {
        return ChronoUnit.DAYS.between(dateTime1, dateTime2);
    }

    /**
     * 根据起始和结束 LocalDateTime，返回所有日期列表（包含起止日期）
     *
     * @param startTime 起始时间（LocalDateTime）
     * @param endTime 结束时间（LocalDateTime）
     */
    public static List<LocalDate> allDatesBetween(LocalDateTime startTime, LocalDateTime endTime) {
        LocalDate startDate = startTime.toLocalDate();
        LocalDate endDate = endTime.toLocalDate();
        if (endDate.isBefore(startDate)) {

            throw new IllegalArgumentException("结束时间不能早于起始时间");
        }
        long daysBetween = ChronoUnit.DAYS.between(startDate, endDate) + 1;
        return IntStream.range(0, (int)daysBetween).mapToObj(startDate::plusDays).collect(Collectors.toList());
    }


    /**
     * 根据起始和结束 LocalDateTime，返回所有日期列表（包含起止日期）
     *
     * @param startTime 起始时间（LocalDateTime）
     *
     * @param endTime 结束时间（LocalDateTime）
     */
    public static <T> void fillMissingDatesByDay(LocalDateTime startTime, LocalDateTime endTime, List<T> data,
                                                 Function<T, LocalDateTime> dateExtractor, Function<LocalDateTime, T> emptyFunction) {
        List<LocalDate> allDatesBetween = allDatesBetween(startTime, endTime);
        Map<LocalDate, T> groupedData =
                data.stream().collect(Collectors.toMap(item -> dateExtractor.apply(item).toLocalDate(), item -> item,
                        (existing, replacement) -> replacement));
        for (LocalDate date : allDatesBetween) {
            T item = groupedData.get(date);
            if (item == null) {
                T apply = emptyFunction.apply(date.atStartOfDay());
                data.add(apply);
            }
        }
    }
}
